%{
#define YYDEBUG  1
#include <math.h>
#include <stdio.h>

#include "ConstantPool.h"
#include "ClassFile.h"
#include "SymbolPool.h"

#include "Ast.h"

int yylex (void);
void yyerror (char const *);

//static ConstantPool * cp;
static AstNode *      ast;
//static SymbolPool *   symbols;
%}

%error-verbose

%union {
  const char *                    constant_literal;
  double                          constant_double;
  class AstNode *                 node;
  class AstNodeStatementList *    nodeList;
  class AstNodeExpression *       nodeExpression;
  class AstNodeBooleanOperator*   nodeBooleanOperator;
  class AstNodeCompareExpression* nodeCompareExpression;
  class AstNodeFuncDefStatement*  nodeFuncDef;
  class AstNodeFuncDefParm*       nodeFuncDefParameter;
}
  
// for the 'if/else statement'
%expect 1

%right '='
%left '-' '+'
%left '*' '/'

%left T_AND T_OR
%left '<' '>' T_GREATER_EQUAL T_LESS_EQUAL  T_EQUAL_EQUAL T_NOT_EQUAL
%right T_NOT

// typed tokens
%token <constant_double>T_DOUBLE
%token <constant_literal>T_LITERAL
%token <constant_literal>T_IDENTIFIER

// typeless tokens
%token T_PRINT
%token T_IF
%token T_PRINTLN
%token T_NEWLINE
%token T_SEMICOLON
%token T_EQUAL_EQUAL
%token T_LESS_EQUAL
%token T_GREATER_EQUAL
%token T_AND
%token T_OR
%token T_NOT
%token T_ELSE
%token T_FOR
%token T_TO
%token T_NEXT
%token T_FUNCTION
%token T_AS
%token T_YELD

// nonterminals
%type  <node>     statement 
%type  <node>     simple_statement 
%type  <node>     print_statement
%type  <node>     input
%type  <node>     if_statement
%type  <node>     for_statement
%type  <node> block_statement
%type  <nodeList> statements_list
%type  <node>                  assignment_statement
%type  <nodeExpression>        expression
%type  <nodeExpression>        literal
%type  <nodeBooleanOperator>   boolean_operator
%type  <nodeCompareExpression> compare_expression
%type  <node>                  lvalue
%type  <nodeFuncDefParameter>  funcdef_parameter

// --- in devel ---
%type  <node>     funcdef_statement

// --- experimental ---



%% 

input
  : statements_list { $$=$1; ast=$$; }
  ;

statements_list
  : statements_list statement
  {
    if($2) {
      $1->addNode($2);
    }
    $$=$1;
  }
  | statement 
  {
    $$=new AstNodeStatementList();
    if($1) {
      $$->addNode($1);
    }
  }
  ;

statement 
  : simple_statement statement_separator
  {
    $$=$1;
  }
  | block_statement
  | if_statement 
  | for_statement 
  ;

simple_statement
  : print_statement
  | assignment_statement 
  | funcdef_statement 
  ;              


statement_separator
  : ';'
  ;

block_statement
  : '{' statements_list '}'
  {
    $$=$2;
  }
  ;

print_statement
  : T_PRINTLN T_LITERAL
  {
    AstNodeLiteral * literal=new AstNodeLiteral($2);
    $$=new AstNodePrintLiteral(literal,true);    
    free((void*)$2);
#if 0
    AstNodeLiteral * literal=new AstNodeLiteral(cp,$2);
    $$=new AstNodePrintLiteral(literal,true);    
    free((void*)$2);
#endif
  }
  | T_PRINTLN expression
  {
    $$=new AstNodePrintExpression($2,true);
  }
  | T_PRINT T_LITERAL
  {
    AstNodeLiteral * literal=new AstNodeLiteral($2);
    $$=new AstNodePrintLiteral(literal,false);    
    free((void*)$2);
  }
  | T_PRINT expression
  {
    $$=new AstNodePrintExpression($2,false);
  }
  ;

assignment_statement
  : lvalue '=' expression
  {
    AstNodeAssignment * stmt=new AstNodeAssignment($1,$3);
    $$=stmt;
  }
  ;

lvalue
  : T_IDENTIFIER
  {
    AstNodeLValue * lvalue=new AstNodeLValue($1);
    $$=lvalue;
    free((void*)$1);
#if 0
    u2 index=symbols->addSymbol($1);
    AstNodeLValue * lvalue=new AstNodeLValue(index);
    $$=lvalue;
    free((void*)$1);
#endif
  }
  ;

if_statement
  : T_IF '(' boolean_operator ')' statement
  {
    $$=new AstNodeIfStatement($3,$5,0);
  }
  | T_IF '(' boolean_operator  ')' statement T_ELSE statement
  {
    $$=new AstNodeIfStatement($3,$5,$7);
  }
  ;

compare_expression 
  : expression '>'             expression        
  {
    $$=new AstNodeCompareExpression($1,$3,
      AstNodeCompareExpression::_greater);
  }
  | expression T_EQUAL_EQUAL  expression        
  {
    $$=new AstNodeCompareExpression($1,$3,
      AstNodeCompareExpression::_equal);
  }
  | expression '<'             expression        
  {
    $$=new AstNodeCompareExpression($1,$3,
      AstNodeCompareExpression::_less);
  }
  | expression T_LESS_EQUAL    expression
  {
    $$=new AstNodeCompareExpression($1,$3,
      AstNodeCompareExpression::_less_equal);
  }
  | expression T_GREATER_EQUAL expression
  {
    $$=new AstNodeCompareExpression($1,$3,
      AstNodeCompareExpression::_greater_equal);
  }
  | expression T_NOT_EQUAL expression
  {
    $$=new AstNodeCompareExpression($1,$3,
      AstNodeCompareExpression::_not_equal);
  }
  ;

boolean_operator
  : boolean_operator T_AND boolean_operator
  {
    $$=new AstNodeBooleanOperator($1,$3,
      AstNodeBooleanOperator::_and);
  }
  | boolean_operator T_OR  boolean_operator
  {
    $$=new AstNodeBooleanOperator($1,$3,
      AstNodeBooleanOperator::_or);
  }
  | T_NOT boolean_operator 
  {
    $$=new AstNodeBooleanOperator($2,0,
      AstNodeBooleanOperator::_not);
  }
  | '(' boolean_operator ')'
  {
     $$=$2;
  }
  | compare_expression
  {
    $$=new AstNodeBooleanOperator($1);
  }
  ;

expression
  /*: T_DOUBLE                         
  { 
    AstNodeExpression * node=
      new AstNodeDoubleConstant(cp,$1);
    $$=node; 
  }*/
  : literal
  | expression '+' expression        
  { 
    AstNodeExpression * node=
      new AstNodeBinaryOperator('+',$1,$3);
    $$=node; 
  }
  | expression '-' expression        
  { 
    AstNodeExpression * node=
      new AstNodeBinaryOperator('-',$1,$3);
    $$=node; 
  }
  | expression '*' expression       
  { 
    AstNodeExpression * node=
      new AstNodeBinaryOperator('*',$1,$3);
    $$=node; 
  }
  | expression '/' expression      
  { 
    AstNodeExpression * node=
      new AstNodeBinaryOperator('/',$1,$3);
    $$=node; 
  }
  | '(' expression ')'            
  { 
    $$ = $2;
  }
  | T_IDENTIFIER
  {
    $$=new AstNodeExpressionVariable($1);
    free((void*)$1);
#if 0
    u2 index=symbols->addSymbol($1);
    $$=new AstNodeExpressionVariable(index);
    free((void*)$1);
#endif
  }
  | funcall_expression
  {
    AstNodeExpression * node=
      new AstNodeDoubleConstant(1234.1234);
    $$=node; 
  }

literal 
  : T_DOUBLE
  {
    AstNodeExpression * node=
      new AstNodeDoubleConstant($1);
    $$=node; 
  }
  ;

funcall_expression
  : T_IDENTIFIER  '(' funcall_parameter_list ')'
  {
    printf("calling function '%s'\n",$1);
    free((void*)$1);
  }

funcall_parameter_list
  : expression
  | expression ',' funcall_parameter_list
  ;

for_statement 
  : T_FOR '(' simple_statement ';' boolean_operator ';' simple_statement ')' statement
  {
    $$=new AstNodeForStatement($3,$5,$7,$9);
  }
  ;
/*
  : T_FOR T_IDENTIFIER '=' expression ',' expression statement_separator
    statements_list
    T_NEXT
  {
    u2 index=symbols->addSymbol($2);
    free((void*)$2);
    AstNodeExpression * step=
      new AstNodeDoubleConstant(cp,1.);
    $$=new AstNodeForStatement(index,$4,$6,step,$8);
  }
  ;*/

funcdef_statement
  : T_FUNCTION T_IDENTIFIER  '(' funcdef_parameter_list ')' block_statement
  {
    $$=0;
#if 0
    $$=new AstNodeFuncDefStatement();
    printf("Defining function '%s'\n",$2);
    free((void*)$2);  
#endif
  }
  ;

funcdef_parameter_list
  : funcdef_parameter  
  | funcdef_parameter ','  funcdef_parameter_list
  ;

funcdef_parameter
  : T_IDENTIFIER
  {
#if 0
    $$=new AstNodeFuncDefParm($1,"D");
#endif
    free((void*)$1);
  }
  | T_IDENTIFIER T_AS T_IDENTIFIER
  {
#if 0
    $$=new AstNodeFuncDefParm($1,$3);
#endif
    free((void*)$1);
    free((void*)$3);
  }
  ;

// ----------- sperimental --------------


%%

extern FILE * yyin;
int parse(
  const char * source_filename,
  AstNode* & out_ast)
{
  int ret;

  FILE *f;
  f=fopen(source_filename,"r");
  if(!f) {
    printf("Fatal: cannot open '%s'\n",source_filename);
    return 1;
  }
  
  //cp=new ConstantPool(); 
  //symbols=new SymbolPool();
  ast=0;

  //cp=in_klass->getConstantPool();

  yyin=f;
  ret=yyparse();

  if(ret==0) {
    out_ast=ast;
  } else {
    if(ast)
     delete ast;
    out_ast=0;
  }
  return ret;
}

extern int yyg_line_no;
void yyerror (char const * text)
{
  printf("ERROR at line %d: '%s'\n",yyg_line_no,text);
}
